package functionalEnrichment.semanticSimilarity.semanticSimilarityCalculator.program.similaritiesProgram;

import functionalEnrichment.semanticSimilarity.semanticSimilarityCalculator.program.*;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import functionalEnrichment.semanticSimilarity.semanticSimilarityCalculator.method.SemanticSimilarityMeasure;
import functionalEnrichment.semanticSimilarity.semanticSimilarityCalculator.method.TermOverlap;
import functionalEnrichment.semanticSimilarity.semanticSimilarityCalculator.parsers.GoaParser;
import functionalEnrichment.semanticSimilarity.semanticSimilarityCalculator.units.AnnotatedGene;
import functionalEnrichment.semanticSimilarity.semanticSimilarityCalculator.units.Ontology;
import functionalEnrichment.semanticSimilarity.semanticSimilarityCalculator.units.OntologyType;
import functionalEnrichment.semanticSimilarity.semanticSimilarityCalculator.utils.FileUtils;

/**
 * This class runs the similarities program of the application (specified by "-p similarities" on the command line).
 * This program outputs all semantic similarities to a file that is
 * specified on the command line using the -s argument.
 * @author Jamie MacPherson
 */
public class SimilaritiesProgram extends Program {

    /**
     * The run method required by <code>Program</code> interfaces that runs the program.
     * This program outputs all semantic similarities, according to teh Term Overlap method, to a file that is
     * specified on the command line using the -s argument.
     */
    @Override
    public void run() {
        File oboFile = (File) argumentMap.get(Argument.OBOFILE);
        OntologyType ontologyType = (OntologyType) argumentMap.get(Argument.ONTOLOGY);
        File goaFile = (File) argumentMap.get(Argument.GOAFILE);
        File similaritiesFile = (File) argumentMap.get(Argument.SIMILARITIESFILE);
        Ontology ontology = Ontology.getOntology(oboFile, ontologyType);
        GoaParser goaParser = new GoaParser(goaFile, ontology);
        Map<String, AnnotatedGene> annotatedGenes = goaParser.getAnnotatedGenes();
        SemanticSimilarityMeasure similarityMeasure = getSemanticSimilarityMeasure();
        try {
            outputAllSimilaritiesToFile(annotatedGenes, similaritiesFile, similarityMeasure);
        } catch (IOException ex) {
            Logger.getLogger(SimilaritiesProgram.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    /**
     * @return the SemanticSimilarity measure in use. This is currently set to Term Overlap, but could equally
     * return a different class that implements semantic similarity.
     */
    private SemanticSimilarityMeasure getSemanticSimilarityMeasure() {
        return new TermOverlap();
    }


    /**
     * Outputs all sematic similarities to the file specified on the command line with the -s argument.
     * Fist all annotated genes are listed, then all non-zero semantic similarities between those
     * genes are listed.
     * @param annotatedGenes annotated genes, with their string identifier as a key.
     * @param similaritiesFile a file that is the destination for the results.
     * @param similarityMeasure the measure of semantic similarity being used.
     * @throws IOException is there is a problem writing to the output file.
     */
    private void outputAllSimilaritiesToFile(Map<String, AnnotatedGene> annotatedGenes, File similaritiesFile, SemanticSimilarityMeasure similarityMeasure) throws IOException {

        BufferedWriter writer = FileUtils.getBufferedWriter(similaritiesFile);
        try {
            outputAnnotatedGeneIds(annotatedGenes.keySet(), writer);
            outputSimilarities(writer, annotatedGenes, similarityMeasure);

        } catch (IOException ex) {
            System.out.println("writing to the similarities file has failed");
            System.out.println("exiting program");
            ex.printStackTrace();
            System.exit(0);
        } finally {
            writer.close();
        }
    }

    /**
     * Given a list of gene IDs (annotated genes) and a BufferedWriter, this method will output those
     * gene IDs using the writer, one per line, under the tag "#genes".
     * @param geneIds a list of gene IDs
     * @param writer the writer to which the gene IDs are appended.
     * @throws IOException if there is a problem writing to this file.
     */

    private void outputAnnotatedGeneIds(Set<String> geneIds, BufferedWriter writer) throws IOException {
        writer.append("#genes\n");
        for (String id : geneIds) {
            writer.append(id + "\n");
        }
        writer.append("\n");
        writer.flush();
    }

    
    /**
     * Given a map containing annotated genes (their gene ID as a key), a BufferedWriter and
     * a method for semantic similarity measuring, this method will output a list of semantic similarities,
     * one per line, in the format "gene_id_1 \t gene_id_2 \t semanticSimilarity", where the first gene id
     * comes before (or equal to) the second in their natural ordering, underneath the tag "#non-zero similarities".
     * @param writer the writer to which the data is appended
     * @param annotatedGenes a map containing annotated genes, their gene ID as the key.
     * @param similarityMeasure the method that is used to calculate semantic similarity
     * @throws IOException if there is a porbl;em appending to the file.
     */
    private void outputSimilarities(BufferedWriter writer, Map<String, AnnotatedGene> annotatedGenes, SemanticSimilarityMeasure similarityMeasure) throws IOException {
        writer.append("#non-zero similarities\n");
        String ids[] = annotatedGenes.keySet().toArray(new String[annotatedGenes.keySet().size()]);
        for (int i = 0; i < ids.length; i++) {
            AnnotatedGene g1 = annotatedGenes.get(ids[i]);
            for (int j = i; j < ids.length; j++) {
                AnnotatedGene g2 = annotatedGenes.get(ids[j]);
                String[] idPair = {g1.getId(), g2.getId()};
                Arrays.sort(idPair);
                Number distance = similarityMeasure.getSimilarity(g1, g2);
                if (!distance.equals(0)) {
                    writer.append(idPair[0] + "\t" + idPair[1] + "\t" + distance + "\n");
                }

            }
            writer.flush();
        }
    }
}
